/******************************************************************************\

file      pRTOS.h
version   2.0
author    Marten Wensink / Wouter van Ooijen
date      01-04-2010

This file provides a simple multiple-FSM style tasking service with
a number of synchronisation mechanisms.

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! None of the synchronization mechanisms is really blocking.             !!
!! A task is only blocked after its update-method has run to completion.  !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

\******************************************************************************/

#ifndef __pRTOS
#define __pRTOS

/***************************************************************************\

Global constants

\***************************************************************************/

   // max. # clients that can wait on mutex
const int MAX_CLIENTS  =  4;

   // makes it easier to specify (interval) times
#define MS * 1000
#define US * 1


/***************************************************************************\

Debugging

\***************************************************************************/

#define global_logging 1

   // The macro HERE transates to a newline, the file-name, ":", and
   // the line-number of the place where the HERE macro appears.
   // This can be used for debug logging.
#define HERE_STR( X ) #X
#define HERE2( F, L ) ( "\n" F ":" HERE_STR( L ))
#define HERE HERE2( __FILE__, __LINE__ )

   // Printing to trace (instead of cout) prepends HERE and a space.
#define trace ( std::cout << HERE << " " )


/***************************************************************************\

cTask

\***************************************************************************/

class cTask {
public:

   // construct a basic (non-periodic) task
   cTask ( const char * tname, int prio );

   // construct a periodic task
   cTask ( const char * tname, int prio, int period );

   // tasks should never be destroyed
   ~cTask ( void ) {
      mkt_fatal (#UNIQUE_ERROR); // cTask destructor called
   }

   // public operations
   void suspend (void);
   void resume (void);
   bool isSuspended (void);
   bool isBlocked (void);
   bool isReady (void);
   bool isTerminated (void);
   void terminate (void);
   const char * getName (void);

   // Abstract operations, must be present in a real task
   virtual void init   (void) = 0;
   virtual void update (void) = 0;

   // Should only be used for blocking by a running periodic task and
   // for blocking a task itself by means of a synchronization mechanism
   void block (void);

   // for a periodic task: get its period
   int getPeriod (void) {
      return period;
   }

   // for debugging: switch logging
   void setLogging (bool log) {
      logging = log;
   }

   void statistics_clear (void) {
      runtime_max = 0;
      activations = 0;
   }

   void statistics_print (std::ostream & stream);

private:
   static const int READY;
   static const int SUSPENDED;
   static const int BLOCKED;
   static const int TERMINATED;

   int priority;
   int status;

      // for a periodic task only: time to wait until the next activation
   int waittime;

      // for a periodic task only: interval between activations
   int period;

      // statistics
   int runtime_max;
   int activations;

      // for debugging
   bool logging;

   // Should only be used for unblocking periodic tasks and
   // for unblocking a task by means of a synchronization mechanism
   void unblock (void);

   // for debugging
   const char * name;
   void debug (const char * msg);

   // for the RTOS to link periodic tasks
   cTask * nextPeriodicTask;

   // for the RTOS to link all tasks
   cTask * nextTask;

   friend class pRTOS;
   friend class cEventFlag;
   template <class T, const int SIZE> friend class cChannel;
   friend class cEventFlagGroup;
   friend class cMutex;
};


/***************************************************************************\

cTimer

\***************************************************************************/

class cTimer {
public:

   cTimer (void);

   // a timer should never be destroyed
   ~cTimer (void) {
      mkt_fatal (#UNIQUE_ERROR); // cTimer destructor called
   }

   void set (int microseconds) {
      nrTicks = microseconds;
   };

   void cancel (void) {
      nrTicks = 0;
   }

   // to be defined in a subclass
   virtual void timeUp (void) = 0;

private:

   // the number of microseconds until the next timeUp
   int nrTicks;

   // called by the RTOS only
   void tick (int elapsed) {
      if( nrTicks > 0 ) {
         nrTicks -= elapsed;
         if (nrTicks <= 0) {
            timeUp();
         }
      }
   }

   // for the RTOS to link timers in a chain
   cTimer * nextTimer;

   friend class pRTOS;
};


/***************************************************************************\

cPool

\***************************************************************************/

template <class T> class cPool {
public:
   // constructor
   cPool (void) : data (0) { }

   // public operations
   void write (T item) {
      data = item;
   }

   T read (void) {
      return data;
   }

private:
   // private attributes
   T data;
};


/***************************************************************************\

cEventFlag

\***************************************************************************/

class cEventFlag {
public:
   // constructor
   cEventFlag ( ) : client (0), flag (false) { std::cout << "\nconstructed flag: " << flag << "\n"; }

   // public operations
   void set (void);
   void wait (void);

private:
   // private attributes
   cTask * client;
   bool flag;
};


/***************************************************************************\

cEventFlagGroup:  The group can have a maximum of 16 flags

                  N.B. The operation getFlagNo() may only be executed ONCE
                       AFTER the execution of a wait() operation.

\***************************************************************************/

class cEventFlagGroup {
public:
   // constructor
   cEventFlagGroup (int size) :
      groupSize (size),
      client (0),
      flags (0),
      flagNo (0)
   { }

   // public operations
   void set (int flag);
   void wait (void);
   int getFlagNo (void);

private:
   // private attributes
   // const
   int groupSize;
   cTask * client;
   unsigned int flags;
   int flagNo;
};


/***************************************************************************\

cMutex

\***************************************************************************/

class cMutex {
public:
   // constructor
   cMutex (void) : owner (0), head (0), tail (-1), qSize (0) { }

   // public operations
   void wait   (void);
   void signal (void);

private:
   // private attributes
   cTask * owner;    // owner of the mutex value
   int head;       // head of the client queue (first to resume)
   int tail;       // tail of the client queue
   int qSize;      // # waiting clients in queue
   cTask * queue[MAX_CLIENTS];
};


/***************************************************************************\

pRTOS

\***************************************************************************/

class cTimer;

class pRTOS {
public:

   // runs the scheduler
   static void run (void);

   // Return the task currently executed
   static cTask * getCurrentTask (void) { 
      return curTask;
   }

      // get elapsed time in micro seconds since OS startup
   static long long int getRunTime (void) {
      return runTime;
   }

      // statistics
   static void statistics_print (std::ostream & stream);
   static void statistics_clear (void) {
      clearing = true;
   }

private:

     // pRTOS is fully static, no need to construct a pRTOS object
   pRTOS (void) { }

      // the list of timers
   static cTimer * timerList;
   static void registerTimer (cTimer * t);

      // the list of periodic tasks
   static cTask * periodicTaskList;
   static void registerTask (cTask * task, int prio, unsigned hz);

     // the list all tasks, highest priority first
   static cTask * taskList;
   static void registerTask (cTask * task, int prio);

     // reference to the task currently executed
   static cTask * curTask;

     // called by run
   static void beat (void);

      // updated by the timer interrupt
   static volatile long long int runTime;
   static volatile int elapsedTime;

      // use by statistics clearance
   static volatile bool clearing;
   static void do_statistics_clear (void);

   friend class cTask;           // must call regsiterTask
   friend class cTimer;          // must call registerTimer
   friend void  timerISR (void); // must update runTime and ElapsedTime
};


/***************************************************************************\

cMailbox: Can be used by 1 posting and 1 pending task

          N.B. The operation getItem() may only be executed ONCE
               AFTER the execution of a pend() operation.

\***************************************************************************/

template <class T> class cMailbox {
public:
   // constructor
   cMailbox (void) : data (0), client (0) { }

   // public operations
   void post (T item) {
      data = item;
      if (client != 0)
         client->unblock();
      client = pRTOS::getCurrentTask();
      client->block();
   }

   void pend (void) {
      if (client == 0) {
         client = pRTOS::getCurrentTask();
         client->block();
      }
   }

   T getItem (void) {
      client->unblock();
      client = 0;
      return data;
   }

private:
   // private attributes
   T data;
   cTask * client;
};


/***************************************************************************\

cChannel: Can be used by 1 reading and >=1 writing tasks

          N.B. The operation getItem() may only be executed ONCE
               AFTER the execution of a read() operation.

\***************************************************************************/

template <class T, const int SIZE> class cChannel {
public:
   // constructor
   cChannel () : qSize (0), head (0), tail (0), reader (0) { }

   // public operations
   void write (T item) {
      queue[head] = item;
      if (++head == SIZE)
         head = 0;
      qSize += 1;
      if (reader != 0) {
         reader->unblock();
         reader = 0;
      }
   }

   void read (void) {
      if (qSize == 0) {
         reader = pRTOS::getCurrentTask();
         reader->block();
      }
   }

   T getItem (void) {
      T buf = queue[tail];
      if (++tail == SIZE)
         tail = 0;
      qSize -= 1;
      return buf;
   }

private:
   // private attributes
   T queue[SIZE];
   int qSize;
   int head;
   int tail;
   cTask * reader;
};

#endif
